package com.vdurmont.emoji;

import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.List;

/**
 * This class represents an emoji.<br>
 * <br>
 * This object is immutable so it can be used safely in a multithreaded context.
 *
 * @author Vincent DURMONT [vdurmont@gmail.com]
 */
public class Emoji {
  private final String description;
  private final boolean supportsFitzpatrick;
  private final List<String> aliases;
  private final List<String> tags;
  private final String unicode;
  private final String htmlDec;
  private final String htmlHex;

  /**
   * Constructor for the Emoji.
   *
   * @param description         The description of the emoji
   * @param supportsFitzpatrick Whether the emoji supports Fitzpatrick modifiers
   * @param aliases             the aliases for this emoji
   * @param tags                the tags associated with this emoji
   * @param bytes               the bytes that represent the emoji
   */
  protected Emoji(
    String description,
    boolean supportsFitzpatrick,
    List<String> aliases,
    List<String> tags,
    byte... bytes
  ) {
    this.description = description;
    this.supportsFitzpatrick = supportsFitzpatrick;
    this.aliases = Collections.unmodifiableList(aliases);
    this.tags = Collections.unmodifiableList(tags);

    int count = 0;
    try {
      this.unicode = new String(bytes, "UTF-8");
      int stringLength = getUnicode().length();
      String[] pointCodes = new String[stringLength];
      String[] pointCodesHex = new String[stringLength];

      for (int offset = 0; offset < stringLength; ) {
        final int codePoint = getUnicode().codePointAt(offset);

        pointCodes[count] = String.format("&#%d;", codePoint);
        pointCodesHex[count++] = String.format("&#x%x;", codePoint);

        offset += Character.charCount(codePoint);
      }
      this.htmlDec = stringJoin(pointCodes, count);
      this.htmlHex = stringJoin(pointCodesHex, count);
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Method to replace String.join, since it was only introduced in java8
   * @param array the array to be concatenated
   * @return concatenated String
   */
  private String stringJoin(String[] array, int count){
    String joined = "";
    for(int i = 0; i < count; i++)
      joined += array[i];
    return joined;
  }

  /**
   * Returns the description of the emoji
   *
   * @return the description
   */
  public String getDescription() {
    return this.description;
  }

  /**
   * Returns wether the emoji supports the Fitzpatrick modifiers or not
   *
   * @return true if the emoji supports the Fitzpatrick modifiers
   */
  public boolean supportsFitzpatrick() {
    return this.supportsFitzpatrick;
  }

  /**
   * Returns the aliases of the emoji
   *
   * @return the aliases (unmodifiable)
   */
  public List<String> getAliases() {
    return this.aliases;
  }

  /**
   * Returns the tags of the emoji
   *
   * @return the tags (unmodifiable)
   */
  public List<String> getTags() {
    return this.tags;
  }

  /**
   * Returns the unicode representation of the emoji
   *
   * @return the unicode representation
   */
  public String getUnicode() {
    return this.unicode;
  }

  /**
   * Returns the unicode representation of the emoji associated with the
   * provided Fitzpatrick modifier.<br>
   * If the modifier is null, then the result is similar to
   * {@link Emoji#getUnicode()}
   *
   * @param fitzpatrick the fitzpatrick modifier or null
   *
   * @return the unicode representation
   * @throws UnsupportedOperationException if the emoji doesn't support the
   * Fitzpatrick modifiers
   */
  public String getUnicode(Fitzpatrick fitzpatrick) {
    if (!this.supportsFitzpatrick()) {
      throw new UnsupportedOperationException(
        "Cannot get the unicode with a fitzpatrick modifier, " +
        "the emoji doesn't support fitzpatrick."
      );
    } else if (fitzpatrick == null) {
      return this.getUnicode();
    }
    return this.getUnicode() + fitzpatrick.unicode;
  }

  /**
   * Returns the HTML decimal representation of the emoji
   *
   * @return the HTML decimal representation
   */
  public String getHtmlDecimal() {
    return this.htmlDec;
  }

  /**
   * @deprecated identical to {@link #getHtmlHexadecimal()} for
   * backwards-compatibility. Use that instead.
   *
   * @return the HTML hexadecimal representation
   */
  public String getHtmlHexidecimal() {
    return this.getHtmlHexadecimal();
  }

  /**
   * Returns the HTML hexadecimal representation of the emoji
   *
   * @return the HTML hexadecimal representation
   */
  public String getHtmlHexadecimal() {
    return this.htmlHex;
  }

  @Override
  public boolean equals(Object other) {
    return !(other == null || !(other instanceof Emoji)) &&
      ((Emoji) other).getUnicode().equals(getUnicode());
  }

  @Override
  public int hashCode() {
    return unicode.hashCode();
  }

  /**
   * Returns the String representation of the Emoji object.<br>
   * <br>
   * Example:<br>
   * <code>Emoji {
   *   description='smiling face with open mouth and smiling eyes',
   *   supportsFitzpatrick=false,
   *   aliases=[smile],
   *   tags=[happy, joy, pleased],
   *   unicode='😄',
   *   htmlDec='&amp;#128516;',
   *   htmlHex='&amp;#x1f604;'
   * }</code>
   *
   * @return the string representation
   */
  @Override
  public String toString() {
    return "Emoji{" +
      "description='" + description + '\'' +
      ", supportsFitzpatrick=" + supportsFitzpatrick +
      ", aliases=" + aliases +
      ", tags=" + tags +
      ", unicode='" + unicode + '\'' +
      ", htmlDec='" + htmlDec + '\'' +
      ", htmlHex='" + htmlHex + '\'' +
      '}';
  }
}
